swadhinbiswas feat: optimize Docker CI/CD builds, refine site-wide UI aesthetic, and remediate secrets · 8/19/2026
c3e5c18
187 lines · 7.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
---
import BaseLayout from "@/layouts/BaseLayout.astro";
import Logo from "@/components/ui/Logo.astro";
import { getDatabase } from "@/db";
import { passwordResetTokens } from "@/db/schema";
import { eq } from "drizzle-orm";
import { Lock, AlertCircle, CheckCircle2, ArrowLeft, Loader2 } from "lucide-react";

const { token } = Astro.params;

if (!token) {
  return Astro.redirect("/login");
}

const db = getDatabase();
const resetToken = await db.query.passwordResetTokens.findFirst({
  where: eq(passwordResetTokens.token, token),
});

const isValid = resetToken && new Date(resetToken.expiresAt) > new Date();
---

<BaseLayout title="Set New Password · OpenCodeHub">
  <div class="flex min-h-[calc(100vh-8rem)] items-center justify-center py-12 px-4">
    <div class="w-full max-w-sm space-y-6">
      <!-- Header -->
      <div class="text-center space-y-2">
        <a href="/" class="inline-flex items-center gap-2.5 hover:opacity-90 transition-opacity">
          <Logo class="h-9 w-9 text-foreground" />
          <span class="text-xl font-bold text-foreground tracking-tight">OpenCodeHub</span>
        </a>
        <h1 class="text-xl font-semibold text-foreground tracking-tight">Set new password</h1>
        <p class="text-xs text-muted-foreground">
          Choose a strong new password for your account.
        </p>
      </div>

      <!-- Card -->
      <div class="rounded-xl border border-border/80 bg-card/60 backdrop-blur-xl p-6 sm:p-7 shadow-sm">
        {
          !isValid ? (
            <div class="space-y-4">
              <div class="rounded-lg border border-red-500/30 bg-red-500/10 p-4 text-xs text-red-400">
                <div class="flex items-start gap-2.5">
                  <AlertCircle className="h-4 w-4 text-red-400 flex-shrink-0 mt-0.5" />
                  <div>
                    <p class="font-semibold text-foreground">Invalid or expired link</p>
                    <p class="text-muted-foreground mt-0.5">
                      This password reset link is invalid or has expired. Please request a new one.
                    </p>
                  </div>
                </div>
              </div>

              <a
                href="/password-reset"
                class="w-full py-2 px-4 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white font-semibold text-sm shadow-sm transition-all flex items-center justify-center gap-1.5 text-center"
              >
                <span>Request new link</span>
              </a>
            </div>
          ) : (
            <form class="space-y-4" id="new-password-form">
              <input type="hidden" name="token" value={token} />

              <div class="space-y-1.5">
                <label for="password" class="block text-xs font-medium text-muted-foreground">
                  New Password
                </label>
                <div class="relative">
                  <Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
                  <input
                    id="password"
                    name="password"
                    type="password"
                    required
                    minlength="8"
                    placeholder="At least 8 characters"
                    autofocus
                    class="w-full rounded-md border border-border bg-background pl-9 pr-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all"
                  />
                </div>
              </div>

              <div class="space-y-1.5">
                <label for="confirmPassword" class="block text-xs font-medium text-muted-foreground">
                  Confirm Password
                </label>
                <div class="relative">
                  <Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
                  <input
                    id="confirmPassword"
                    name="confirmPassword"
                    type="password"
                    required
                    minlength="8"
                    placeholder="Repeat new password"
                    class="w-full rounded-md border border-border bg-background pl-9 pr-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all"
                  />
                </div>
              </div>

              <div id="error-message" class="text-xs text-red-400 bg-red-500/10 border border-red-500/20 rounded-md p-2.5 hidden">
              </div>

              <button
                type="submit"
                id="submit-btn"
                class="w-full py-2 px-4 rounded-md bg-[#238636] hover:bg-[#2ea043] text-white font-semibold text-sm shadow-sm transition-all focus:outline-none focus:ring-2 focus:ring-primary/40 flex items-center justify-center gap-2 disabled:opacity-60"
              >
                <span id="btn-text">Reset Password</span>
                <Loader2 id="btn-spinner" className="h-4 w-4 animate-spin hidden" />
              </button>
            </form>
          )
        }
      </div>

      <!-- Footer Link -->
      <div class="rounded-xl border border-border bg-card/40 p-4 text-center text-xs text-muted-foreground shadow-xs">
        <a href="/login" class="font-semibold text-foreground hover:underline inline-flex items-center gap-1">
          <ArrowLeft className="h-3 w-3" />
          <span>Back to Sign in</span>
        </a>
      </div>
    </div>
  </div>
</BaseLayout>

<script>
  const form = document.getElementById("new-password-form") as HTMLFormElement;
  const submitBtn = document.getElementById("submit-btn") as HTMLButtonElement;
  const btnText = document.getElementById("btn-text");
  const btnSpinner = document.getElementById("btn-spinner");
  const errorMsg = document.getElementById("error-message");

  form?.addEventListener("submit", async (e) => {
    e.preventDefault();
    const formData = new FormData(form);
    const data = Object.fromEntries(formData);

    if (errorMsg) errorMsg.classList.add("hidden");

    if (data.password !== data.confirmPassword) {
      if (errorMsg) {
        errorMsg.textContent = "Passwords do not match";
        errorMsg.classList.remove("hidden");
      }
      return;
    }

    if (submitBtn) submitBtn.disabled = true;
    btnText?.classList.add("hidden");
    btnSpinner?.classList.remove("hidden");

    try {
      const response = await fetch("/api/auth/reset-password", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
      });

      if (response.ok) {
        window.location.href = "/login?reset=success";
      } else {
        const error = await response.json().catch(() => null);
        if (errorMsg) {
          errorMsg.textContent = error?.message || "Failed to reset password";
          errorMsg.classList.remove("hidden");
        }
      }
    } catch (error) {
      console.error("Error resetting password:", error);
      if (errorMsg) {
        errorMsg.textContent = "An error occurred while resetting password";
        errorMsg.classList.remove("hidden");
      }
    } finally {
      if (submitBtn) submitBtn.disabled = false;
      btnText?.classList.remove("hidden");
      btnSpinner?.classList.add("hidden");
    }
  });
</script>